package {

    import mx.collections.ArrayCollection;
    import mx.collections.IList;
    import mx.events.CollectionEvent;
    import mx.events.CollectionEventKind;
    import mx.events.PropertyChangeEvent;

    /**
     * Wraps an existing ArrayCollection so that the underlying contents of this
     * collection and that collection are always identical, but this one can be filtered
     * and sorted without affecting the original
     */
    public class ArrayCollectionHelper extends ArrayCollection {

        //-----------------------------------------------------------------
        //
        //
        // Variables
        //
        //
        //-----------------------------------------------------------------

        /**
         *  <pre>addUpdateItemFunction(item:Object, index:uint):Object</pre>
         */
        public var addUpdateItemFunction:Function;

        /**
         *  The CollectionEvent.COLLECTION_CHANGE function hanlder
         */
        public var collectionChangeFunction:Function;

        /**
         *  The source ArrayCollection.
         */
        private var _collection:ArrayCollection;

        private var _autoRefresh:Boolean;

        //-----------------------------------------------------------------
        //
        //
        // Constructor
        //
        //
        //-----------------------------------------------------------------

        public function ArrayCollectionHelper(collection:ArrayCollection=null, autoRefresh:Boolean=true) {
            super();
            addUpdateItemFunction = defaultAddUpdateItemFunction;
            dataProvider = collection;
            _autoRefresh = autoRefresh;
        }

        //-----------------------------------------------------------------
        //
        //
        // Getters and Setters
        //
        //
        //-----------------------------------------------------------------

        [Bindable]
        public function set dataProvider(collection:ArrayCollection):void {
            if (_collection) {
                _collection.removeEventListener(CollectionEvent.COLLECTION_CHANGE, collectionEventHandler);
            }

            _collection = collection;

            if (_collection == null) {
                _collection = new ArrayCollection();
            } else {
                this.source = clone(_collection.list.toArray());
            }

            _collection.addEventListener(CollectionEvent.COLLECTION_CHANGE, collectionEventHandler, false, 0, true);
        }

        public function get dataProvider():ArrayCollection {
            return _collection;
        }

        //-----------------------------------------------------------------
        //
        //
        // Methods
        //
        //
        //-----------------------------------------------------------------

        protected function collectionEventHandler(event:CollectionEvent):void {

            var item:Object;
            var items:Array = event.items;

            switch (event.kind) {
                case CollectionEventKind.ADD:
                    for each (item in items) {
                        this.source.splice(event.location, 0, addUpdateItemFunction(item, event.location));
                    }
                    this.refreshing();
                    break;
                case CollectionEventKind.REPLACE:
                    item = PropertyChangeEvent(items[0]).newValue;
                    this.source[event.location] = addUpdateItemFunction(item, event.location);
                    this.refreshing();
                    break;
                case CollectionEventKind.REMOVE:
                    this.source.splice(event.location, 1);
                    this.refreshing();
                    break;
                case CollectionEventKind.RESET:
                    this.source = clone(items.length == 0 ? _collection.list.toArray() : items);
                    break;
                default:
                    break;
            }

            if (collectionChangeFunction != null) {
                collectionChangeFunction(event);
            }
            dispatchEvent(event);
        }

        protected function clone(items:Array):Array {
            var array:Array = new Array(items.length);
            for (var index:uint = 0; index < items.length; index++) {
                array[index] = addUpdateItemFunction(items[index], index);
            }
            return array;
        }

        protected function defaultAddUpdateItemFunction(item:Object, index:uint):Object {
            return item;
        }

        protected function refreshing():void {
            if (_autoRefresh == true) {
                this.refresh();
            }
        }

        /**
         *  The IList that this collection view wraps.
         */
        public function setList(value:IList):void {
            _collection.list = value;
        }

        //-----------------------------------------------------------------
        //
        //
        // Overrides
        //
        //
        //-----------------------------------------------------------------

        /**
         *  @deprecated Use setList
         */
        override public function set list(value:IList):void {
            super.list = value;
        }

        /**
         *  Adds the item at the specified index.
         *  The index of any item greater than the index of the added item is increased by one.
         *  If the the specified index is less than zero or greater than the length
         *  of the list, a RangeError is thrown.
         *
         *  @param item The item to place at the index.
         *
         *  @param index The index at which to place the item.
         *
         *  @throws RangeError if index is less than 0 or greater than the length of the list.
         */
        override public function addItemAt(item:Object, index:int):void {
            if (index < length) {
                var sourceIndex:int = _collection.getItemIndex(this.getItemAt(index));
                _collection.addItemAt(item, sourceIndex);
            } else {
                _collection.addItem(item);
            }
        }

        /**
         *  Removes the item at the specified index and returns it.
         *  Any items that were after this index are now one index earlier.
         *
         *  @param index The index from which to remove the item.
         *
         *  @return The item that was removed.
         *
         *  @throws RangeError is index is less than 0 or greater than length.
         */
        override public function removeItemAt(index:int):Object {
            var sourceIndex:int = _collection.getItemIndex(this.getItemAt(index));
            return _collection.removeItemAt(sourceIndex);
        }

        /**
         *  Places the item at the specified index.
         *  If an item was already at that index the new item will replace it
         *  and it will be returned.
         *
         *  @param item The new item to be placed at the specified index.
         *
         *  @param index The index at which to place the item.
         *
         *  @return The item that was replaced, or <code>null</code> if none.
         *
         *  @throws RangeError if index is less than 0 or greater than length.
         */
        override public function setItemAt(item:Object, index:int):Object {
            var sourceIndex:int = _collection.getItemIndex(this.getItemAt(index));
            return _collection.setItemAt(item, sourceIndex);
        }
    }
}